/*
 * $Id: ZipperUtil.java,v 1.8 2010/09/21 19:08:40 navdeep_mahajan Exp $
 * 
 * Copyright (C) 2008 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.common.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.apache.tools.tar.TarEntry;
import org.apache.tools.tar.TarInputStream;
import org.apache.tools.tar.TarOutputStream;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.ASCUtil;
import com.ge.healthcare.autosc.common.exceptions.ZipperUtilException;

public class ZipperUtil {

	private static final int BUF_SIZE = 1024;
	private static final String TAR_GZIP_EXT = ".tar.gz";
	private static final String GZIP_EXT = ".gz";

	/**
	 * Gzip the input file to an archive with the same name except for ".gz"
	 * appended to it. The archive will be in the chosen output directory.
	 **/
	public static void gzipFile(File inFile, File outDir)
			throws ZipperUtilException {

		String methodName = "gzipFile";
		// Create the output gzip archive from the name of the input file
		// and the output directory. Add the ".gz" file type at the end.
		File gzip_output = new File(outDir, inFile.getName() + ".gz");

		// Create a buffered gzip output stream to the archive.
		GZIPOutputStream gzip_out_stream = null;
		try{
			try {
				FileOutputStream out = new FileOutputStream(gzip_output);
				gzip_out_stream = new GZIPOutputStream(
						new BufferedOutputStream(out));
			} catch (IOException e) {
				String errorStr = "Failed gzip file " + inFile.getPath();
				ASCLogger.error(ZipperUtil.class, methodName, errorStr, e);
				throw new ZipperUtilException(errorStr, e);
			}
	
			// Now compress the file using the GZIP output stream.
			// Need to use a buffer to read the file.
			byte[] input_buffer = new byte[BUF_SIZE];
			int len = 0;
			BufferedInputStream source = null;
			try {
				// Create a buffered stream from the input file.
				FileInputStream in = new FileInputStream(inFile);
				source = new BufferedInputStream(in, BUF_SIZE);
	
				// Read from the input stream and write to the gzip output stream
				// which will compress the output before writing to the output file.
				while ((len = source.read(input_buffer, 0, BUF_SIZE)) != -1)
					gzip_out_stream.write(input_buffer, 0, len);
				in.close();
			} catch (IOException e) {
				String errorStr = "Failed gzip file " + inFile.getPath();
				ASCLogger.error(ZipperUtil.class, methodName, errorStr, e);
				throw new ZipperUtilException(errorStr, e);
			} finally{
				ASCFileHandler.closeInputStream(source);
			}
		}finally{
			// Close up the output file
			try {
				if (!ASCUtil.isNullOrEmptyObject(gzip_out_stream)){
					gzip_out_stream.close();
				}
			} catch (IOException e) {
				ASCLogger.warn(ZipperUtil.class, methodName,
						"Error closing gzip file " + gzip_output.getPath());
			}
		}
	} // gzipFile

	/**
	 * Gunzip the input archive. Send the output to the directory specified by
	 * dir_output. Assumes that the input file name ends with ".gz"
	 **/
	public static void gunzipFile(File inFile, File outFile)
			throws ZipperUtilException {
		String methodName = "gunzipFile";
		// Create a buffered gzip input stream to the archive file.
		GZIPInputStream gzip_in_stream = null;
		try {
			FileInputStream in = new FileInputStream(inFile);
			BufferedInputStream source = new BufferedInputStream(in);
			gzip_in_stream = new GZIPInputStream(source);

			// Use the name of the archive for the output file name but
			// with ".gz" stripped off.
			FileOutputStream out = new FileOutputStream(outFile);
			BufferedOutputStream destination = new BufferedOutputStream(out,
					BUF_SIZE);
			decompress(gzip_in_stream, destination);
		} catch (IOException ioe) {
			String errorStr = "Failed gunzip file " + inFile.getPath();
			ASCLogger.error(ZipperUtil.class, methodName, errorStr, ioe);
			throw new ZipperUtilException(errorStr, ioe);
		} finally{
			ASCFileHandler.closeInputStream(gzip_in_stream);
		}

	}

	/**
	 * Open the file as Zip file. Caller is responsible to close the stream.
	 * 
	 * @param file
	 * @return
	 */
	public static GZIPInputStream openAsZip(File file) {
		FileInputStream rawStream = null;
		GZIPInputStream zipStream = null;
		try {
			rawStream = new FileInputStream(file);
			zipStream = new GZIPInputStream(new BufferedInputStream(rawStream));
		} catch (IOException ioe) {
			ASCUtil.silentlyClose(zipStream);
			ASCUtil.silentlyClose(rawStream);
		}
		return zipStream;
	}

	public static void gunzipFile(InputStream inputStream, File outFile)
			throws ZipperUtilException {
		String methodName = "gunzipFile";
		// Create a buffered gzip input stream to the archive file.
		GZIPInputStream gzip_in_stream;
		try {
			BufferedInputStream source = new BufferedInputStream(inputStream);
			gzip_in_stream = new GZIPInputStream(source);
			// Create a buffered output stream to the file.
			FileOutputStream out = new FileOutputStream(outFile);
			BufferedOutputStream destination = new BufferedOutputStream(out,
					BUF_SIZE);
			decompress(gzip_in_stream, destination);
		} catch (IOException ioe) {
			String errorStr = "Failed gunzip file";
			ASCLogger.error(ZipperUtil.class, methodName, errorStr, ioe);
			throw new ZipperUtilException(errorStr, ioe);
		}
	}

	public static void untarGunzipFile(File srcFile, File destDir)
			throws ZipperUtilException {
		String methodName = "untarGunzipFile";

		// Unzip the file
		File outFile = new File(destDir,
				getTarGunzipFileName(srcFile.getName()));
		gunzipFile(srcFile, outFile);

		TarInputStream tis = null;
		try {
			tis = new TarInputStream(new FileInputStream(outFile));
			TarEntry tarEntry = tis.getNextEntry();
			while (tarEntry != null) {
				// create a file with the same name as the tarEntry
				File destPath = new File(destDir, tarEntry.getName());
				ASCLogger.info(ZipperUtil.class, "untarGunzipFile",
						"tar Entry " + tarEntry.getName());
				if (tarEntry.isDirectory()) {
					destPath.mkdir();
				} else {
					FileOutputStream fout = new FileOutputStream(destPath);
					tis.copyEntryContents(fout);
					fout.close();
				}
				tarEntry = tis.getNextEntry();
			}
		} catch (Exception e) {
			String errorStr = "Failed untar the file " + outFile.getPath();
			ASCLogger.error(ZipperUtil.class, methodName, errorStr, e);
			throw new ZipperUtilException(errorStr, e);
		} finally {
			if (tis != null) {
				try {
					tis.close();
				} catch (IOException ioe) {
					// Nothing can do..
				}
			}
			// delete the temp file
			if (outFile.exists()) {
				outFile.delete();
			}
		}
	}

	/**
	 * Tar a file
	 * 
	 * @param srcDir
	 *            directory where files will be tar
	 * @param tarFile
	 *            tar file name
	 * @throws ZipperUtilException
	 */
	public static void tarFile(File srcDir, File tarFile)
			throws ZipperUtilException {
		String methodName = "tarFile";
		try {
			TarOutputStream tos = new TarOutputStream(new FileOutputStream(
					tarFile));
			tar(srcDir.getAbsolutePath(), srcDir, tos);
			tos.close();
		} catch (IOException ioe) {
			String errorStr = "Failed tar the file in directory"
					+ srcDir.getPath();
			ASCLogger.error(ZipperUtil.class, methodName, errorStr, ioe);
			throw new ZipperUtilException(errorStr, ioe);
		}

	}

	/**
	 * Determine if the file has "*.tar.gz" extension
	 * 
	 * @param file
	 * @return
	 */
	public static boolean isTarGzipFile(File file) {
		String fName = file.getName();
		if (fName.length() <= TAR_GZIP_EXT.length()) {
			return false;
		}
		String file_extension = fName.substring(fName.length()
				- TAR_GZIP_EXT.length());
		return (TAR_GZIP_EXT.equals(file_extension));
	}

	/**
	 * Determine if the file has "*.tar.gz" extension
	 * 
	 * @param file
	 * @return
	 */
	public static boolean isTarGzipFile(String fName) {
		if (fName.length() <= TAR_GZIP_EXT.length()) {
			return false;
		}
		String file_extension = fName.substring(fName.length()
				- TAR_GZIP_EXT.length());
		return (TAR_GZIP_EXT.equals(file_extension));
	}

	public static boolean isTarZipFormat(File fName) {
		boolean isTarZip = false;
		InputStream zipStream = null;
		TarInputStream tis = null;
		try {
			zipStream = openAsZip(fName);
			if (zipStream != null) {
				// Try to open the tar stream and attempt to take one entry out
				// from it
				tis = new TarInputStream(zipStream);
				tis.getNextEntry();
				isTarZip = true;
			}
		} catch (IOException ioe) {
			ASCLogger.warn(ZipperUtil.class, "isTarZipFormat",
					"Exception while checking file " + fName.getName()
							+ "is tarzip format", ioe);
		} finally {
			ASCUtil.silentlyClose(tis);
			ASCUtil.silentlyClose(zipStream);
		}
		return isTarZip;
	}

	/**
	 * Check if the file is in zipped format.
	 * 
	 * @param fName
	 * @return
	 */
	public static boolean isZipFormat(File fName) {
		InputStream inputStream = null;
		boolean isZipped = false;
		try {
			inputStream = openAsZip(fName);
			if (inputStream != null) {
				isZipped = true;
			}
		} finally {
			ASCUtil.silentlyClose(inputStream);
		}
		return isZipped;
	}

	/**
	 * Return the gunzip file name. This assume the gzip file has ".gz"
	 * extension.
	 * 
	 * @param fName
	 * @return
	 */
	public static String getTarGunzipFileName(String fName) {
		String tarFileName = null;
		if (isTarGzipFile(fName) == true) {
			tarFileName = fName
					.substring(0, fName.length() - GZIP_EXT.length());
		}
		return tarFileName;
	}

	private static void decompress(GZIPInputStream source,
			BufferedOutputStream target) throws ZipperUtilException {
		String methodName = "decompress";
		// Decompress the gzipped file by reading it via
		// the GZIP input stream. Will need a buffer.
		byte[] input_buffer = new byte[BUF_SIZE];
		int len = 0;
		try {
			// Now read from the gzip stream, which will decompress the data,
			// and write to the output stream.
			while ((len = source.read(input_buffer, 0, BUF_SIZE)) != -1)
				target.write(input_buffer, 0, len);
			target.flush(); // Insure that all data is written to the output.
			target.close();
		} catch (IOException ioe) {
			String errorStr = "Failed gunzip file";
			ASCLogger.error(ZipperUtil.class, methodName, errorStr, ioe);
			throw new ZipperUtilException(errorStr, ioe);
		}
	}

	private static void tar(String rootPath, File srcDir, TarOutputStream tos)
			throws IOException {
		File[] fileList = srcDir.listFiles();

		byte[] buf = new byte[BUF_SIZE];
		for (int i = 0; i < fileList.length; i++) {
			if (fileList[i].isDirectory()) {
				tar(rootPath, fileList[i], tos);
				continue;
			}

			String filePath = fileList[i].getAbsolutePath();
			if (filePath.startsWith(rootPath)) {
				filePath = filePath.substring(rootPath.length());
			}
			FileInputStream fis = new FileInputStream(fileList[i]);
			TarEntry te = new TarEntry(filePath);
			te.setSize(fileList[i].length());
			tos.setLongFileMode(TarOutputStream.LONGFILE_GNU);
			tos.putNextEntry(te);
			int count = 0;
			while ((count = fis.read(buf, 0, BUF_SIZE)) != -1) {
				tos.write(buf, 0, count);
			}
			tos.closeEntry();
			fis.close();
		}
	}
}
